/*
** $Id: ltm.c,v 2.38.1.1 2017/04/19 17:39:34 roberto Exp $
** Tag methods
** See Copyright Notice in lua.h
*/

#define ltm_c
#define LUA_CORE

#include "lprefix.h"


#include <string.h>

#include "lua.h"

#include "ldebug.h"
#include "ldo.h"
#include "lobject.h"
#include "lstate.h"
#include "lstring.h"
#include "ltable.h"
#include "ltm.h"
#include "lvm.h"

static const char udatatypename[] = "userdata";

/**
 * 全局常量数组，用于存储Lua中所有类型的名称字符串。
 * 数组索引对应Lua中的类型标签（LUA_T*），数组元素为对应类型的名称。
 * 注意：
 * - "udatatypename" 表示用户自定义类型的名称。
 * - 最后一个元素 "proto" 仅用于测试目的。
 */
LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTAGS] = {
    "no value",
    "nil", "boolean", udatatypename, "number",
    "string", "table", "function", udatatypename, "thread",
    "proto" /* this last case is used for tests only */
};

/**
 * 初始化 Lua 表元方法名称。
 *
 * 此函数用于初始化 Lua 表中所有预定义的元方法名称（如 "__index"、"__newindex" 等），
 * 并将这些名称存储在全局状态中。这些名称在 Lua 运行时用于标识和调用表的元方法。
 *
 * @param L 指向 Lua 状态的指针。
 *
 * 注意：
 * - 这些元方法名称是 Lua 内部使用的，不会被垃圾回收器回收。
 * - 函数内部通过循环遍历预定义的元方法名称数组，为每个名称创建字符串对象并固定其内存。
 */
void luaT_init(lua_State *L)
{
    static const char *const luaT_eventname[] = {/* ORDER TM */
                                                 "__index", "__newindex",
                                                 "__gc", "__mode", "__len", "__eq",
                                                 "__add", "__sub", "__mul", "__mod", "__pow",
                                                 "__div", "__idiv",
                                                 "__band", "__bor", "__bxor", "__shl", "__shr",
                                                 "__unm", "__bnot", "__lt", "__le",
                                                 "__concat", "__call"};
    int i;
    for (i = 0; i < TM_N; i++) {
        G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]);
        // 将这些元方法名称从allgc移动到fixedgc链表中，防止被垃圾回收器回收
        luaC_fix(L, obj2gco(G(L)->tmname[i]));  /* never collect these names */
    }
}

/*
** function to be used with macro "fasttm": optimized for absence of
** tag methods
*/
/**
 * 获取指定事件的元方法。
 *
 * @param events 包含元方法的表。
 * @param event 事件类型（TMS枚举值）。
 * @param ename 事件名称的字符串。
 * @return 如果找到元方法，则返回对应的TValue指针；否则返回NULL，并缓存该事件无元方法的事实。
 * @note 该函数会断言事件类型不超过TM_EQ。
 */
const TValue *luaT_gettm(Table *events, TMS event, TString *ename)
{
    const TValue *tm = luaH_getshortstr(events, ename);
    lua_assert(event <= TM_EQ);
    if (ttisnil(tm)) {  /* no tag method? */
        events->flags |= cast_byte(1u << event);  /* cache this fact */
        return NULL;
    } else return tm;
}

/**
 * 根据对象和事件类型获取对应的元方法。
 *
 * @param L Lua状态机指针。
 * @param o 需要查询元方法的对象。
 * @param event 元方法事件类型（如__index、__newindex等）。
 * @return 返回对象的元方法，如果不存在则返回nil。
 *
 * @note 该函数用于从对象的元表中查找指定事件的元方法。
 *       支持的表类型包括普通表（LUA_TTABLE）和用户数据（LUA_TUSERDATA），
 *       其他类型会从全局元表中查找。
 */
const TValue *luaT_gettmbyobj(lua_State *L, const TValue *o, TMS event)
{
    Table *mt;
    switch (ttnov(o))
    {
    case LUA_TTABLE:
        mt = hvalue(o)->metatable;
        break;
    case LUA_TUSERDATA:
        mt = uvalue(o)->metatable;
        break;
    default:
        mt = G(L)->mt[ttnov(o)];
    }
    return (mt ? luaH_getshortstr(mt, G(L)->tmname[event]) : luaO_nilobject);
}

/*
** Return the name of the type of an object. For tables and userdata
** with metatable, use their '__name' metafield, if present.
*/
/**
 * 获取给定 TValue 对象的类型名称。
 *
 * 该函数首先检查对象是否有元表，并尝试从元表中获取 "__name" 字段作为类型名称。
 * 如果 "__name" 字段不存在或不是字符串，则返回标准类型名称。
 *
 * @param L Lua 状态机。
 * @param o 指向 TValue 对象的指针。
 * @return 返回对象的类型名称字符串。
 */
const char *luaT_objtypename(lua_State *L, const TValue *o)
{
    Table *mt;
    if ((ttistable(o) && (mt = hvalue(o)->metatable) != NULL) ||
        (ttisfulluserdata(o) && (mt = uvalue(o)->metatable) != NULL))
    {
        const TValue *name = luaH_getshortstr(mt, luaS_new(L, "__name"));
        if (ttisstring(name))  /* is '__name' a string? */
            return getstr(tsvalue(name));  /* use it as type name */
    }
    return ttypename(ttnov(o));  /* else use standard type name */
}

/**
 * 调用元方法（TM，即 metamethod）。
 *
 * 该函数用于调用一个元方法，处理函数调用时的元方法逻辑。它会将函数和参数压入栈，
 * 并根据是否有返回值决定是否将结果移动到指定位置。
 *
 * @param L Lua 状态机指针。
 * @param f 要调用的元方法函数。
 * @param p1 第一个参数。
 * @param p2 第二个参数。
 * @param p3 第三个参数或用于存储结果的指针。
 * @param hasres 指示是否有返回值的标志（1 表示有返回值，0 表示无返回值）。
 *
 * @note 如果是从 Lua 代码调用，元方法可能会 yield；否则不会 yield。
 */
void luaT_callTM(lua_State *L, const TValue *f, const TValue *p1,
                 const TValue *p2, TValue *p3, int hasres)
{
    ptrdiff_t result = savestack(L, p3);
    StkId func = L->top;
    setobj2s(L, func, f);  /* push function (assume EXTRA_STACK) */
    setobj2s(L, func + 1, p1);  /* 1st argument */
    setobj2s(L, func + 2, p2);  /* 2nd argument */
    L->top += 3;
    if (!hasres)  /* no result? 'p3' is third argument */
            setobj2s (L, L->top++, p3);  /* 3rd argument */
    /* metamethod may yield only when called from Lua code */
    if (isLua(L->ci))
        luaD_call(L, func, hasres);
    else
        luaD_callnoyield(L, func, hasres);
    if (hasres) {  /* if has result, move it to its place */
        p3 = restorestack(L, result);
        setobjs2s(L, p3, --L->top);
    }
}

/**
 * 调用二元元方法（Binary Metamethod）。
 *
 * 该函数用于处理两个操作数之间的二元操作（如加法、减法等），通过查找并调用相应的元方法来实现。
 *
 * @param L Lua状态机指针。
 * @param p1 第一个操作数的值。
 * @param p2 第二个操作数的值。
 * @param res 存储结果的栈位置。
 * @param event 元方法事件类型（如TM_ADD、TM_SUB等）。
 * @return 如果成功调用元方法返回1，否则返回0。
 *
 * @note 如果第一个操作数没有对应的元方法，会尝试从第二个操作数中查找。
 */
int luaT_callbinTM(lua_State *L, const TValue *p1, const TValue *p2,
                   StkId res, TMS event)
{
    const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */
    if (ttisnil(tm))
        tm = luaT_gettmbyobj(L, p2, event);  /* try second operand */
    if (ttisnil(tm)) return 0;
    luaT_callTM(L, tm, p1, p2, res, 1);
    return 1;
}

/**
 * @brief 尝试调用二元元方法（binary metamethod）来处理两个值的操作。
 *
 * 该函数用于在Lua中处理两个值的二元操作（如算术运算、位运算等）。如果操作无法直接执行，
 * 则会尝试调用对应的元方法（metamethod）来完成操作。如果元方法调用失败，则会根据操作类型
 * 抛出相应的错误。
 *
 * @param L Lua状态机指针。
 * @param p1 第一个操作数的值。
 * @param p2 第二个操作数的值。
 * @param res 存储操作结果的栈位置。
 * @param event 操作类型（TMS枚举值，如TM_CONCAT、TM_BAND等）。
 *
 * @note 如果操作无法完成且没有对应的元方法，函数会抛出错误，不会返回。
 */
void luaT_trybinTM(lua_State *L, const TValue *p1, const TValue *p2,
                   StkId res, TMS event)
{
    if (!luaT_callbinTM(L, p1, p2, res, event))
    {
        switch (event) {
            case TM_CONCAT:
                luaG_concaterror(L, p1, p2);
                /* call never returns, but to avoid warnings: *//* FALLTHROUGH */
            case TM_BAND:
            case TM_BOR:
            case TM_BXOR:
            case TM_SHL:
            case TM_SHR:
            case TM_BNOT: {
                lua_Number dummy;
                if (tonumber(p1, &dummy) && tonumber(p2, &dummy))
                    luaG_tointerror(L, p1, p2);
                else
                    luaG_opinterror(L, p1, p2, "perform bitwise operation on");
            }
                /* calls never return, but to avoid warnings: *//* FALLTHROUGH */
            default:
                luaG_opinterror(L, p1, p2, "perform arithmetic on");
        }
    }
}

/**
 * 调用元方法以比较两个值的顺序。
 *
 * @param L Lua状态机指针。
 * @param p1 第一个参与比较的值。
 * @param p2 第二个参与比较的值。
 * @param event 元方法事件类型（如 "__lt" 或 "__le"）。
 * @return 如果比较成功且结果为真，返回 1；如果比较失败或结果为假，返回 0；如果没有元方法，返回 -1。
 *
 * @note 此函数通过调用 `luaT_callbinTM` 来执行实际的元方法调用，并根据结果返回相应的值。
 */
int luaT_callorderTM(lua_State *L, const TValue *p1, const TValue *p2,
                     TMS event)
{
    if (!luaT_callbinTM(L, p1, p2, L->top, event))
        return -1;  /* no metamethod */
    else
        return !l_isfalse(L->top);
}
